package sismol.embarcado;

import sismol.data.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import sismol.cartography.*;

public class NMEAParser {
    Posicao pAnterior;

    /**
     * Analisa uma mensagem no padrão NMEA v 0.183, verificando se ela
     * é do tipo RMC. Em caso positivo, retorna informações sobre a posição do
     * veículo.
     * @param Mensagem NMEA
     * @throws NMEAException se a mensagem recebida não estiver corretamente
     * formatada
     */
    public Posicao parsePosicao(String msg) throws NMEAException {
        Double latitude = 0d;
        Double longitude = 0d;
        Integer horario = 0;
        Double velocidade = 0d;

        Pattern patternLatitude = Pattern.compile("(?<=GPRMC,.{13})[0-9]*.[0-9]*,[A-Z]");
        Pattern patternLongitude = Pattern.compile("(?<=GPRMC,.{13}.{12})[0-9]*.[0-9]*,[A-Z]");
        Pattern patternHorario = Pattern.compile("(?<=GPRMC,)[0-9]{6}\\.[0-9]{3}");
        Pattern patternVelocidade = Pattern.compile("(?<=GPRMC,.{13}.{25})[0-9]*.[0-9]*");
        Pattern patternValidade = Pattern.compile("(?<=GPRMC,.{11})[A|V]");
        //TODO usar validade para descartar mensagens inválidas
        
        Matcher matcher = patternLatitude.matcher(msg);
        if (matcher.find()) {
            latitude = parseLatitude(matcher.group());
        }

        matcher = patternLongitude.matcher(msg);
        if (matcher.find()) {
            longitude = parseLongitude(matcher.group());
        }

        matcher = patternHorario.matcher(msg);
        if (matcher.find()) {
            horario = parseHorario(matcher.group());
        }

        matcher = patternVelocidade.matcher(msg);
        if (matcher.find()) {
            velocidade = parseVelocidade(matcher.group());
        }

        Ponto p = new Ponto();
        p.setLatitude(latitude);
        p.setLongitude(longitude);

        Posicao pos = new Posicao();
        pos.setHorario(horario);
        pos.setPosicao(p);
        pos.setVelocidadeMedia(velocidade);
        return pos;

    }

    /*
     * Retorna o horário de uma mensagem NMEA, convertendo-o para timestamp
     * @param horarioString - Parte da mensagem NMEA que contém informação sobre horário
     * @throws NMEAException se a mensagem recebida não estiver corretamente
     * formatada
     */
    private Integer parseHorario(String horarioString) throws NMEAException {
        Integer horario;
        if (horarioString.length() == 10) {
            horarioString = horarioString.replace(".", "");

            try {
                // TODO Converter para timestamp
                horario = Integer.parseInt(horarioString);
            } catch (NumberFormatException e) {
                throw new NMEAException();
            }
            return horario;
        } else {
            throw new NMEAException();
        }

    }

    /*
     * Retorna a longitude de uma mensagem NMEA, convertendo-a de graus decimais
     * para graus-minutos decimais.
     * @param longString - Parte da mensagem NMEA que contém informação sobre longitude
     * @throws NMEAException se a mensagem recebida não estiver corretamente
     * formatada
     */
    private Double parseLongitude(String longString) throws NMEAException {
        if (longString.length() == 12) {
            Double coordinate;
            Double decimalMinutes;
            char hemisphere = longString.charAt(11);
            longString = longString.replace(".", "");

            int degrees = Integer.parseInt(longString.substring(0, 3));

            try {
                decimalMinutes = Double.parseDouble(longString.substring(3, 9)) / 10000;
            } catch (NumberFormatException e) {
                throw new NMEAException();
            }
            coordinate = degrees + decimalMinutes / 60;

            if (hemisphere == 'W') {
                coordinate = -coordinate;
            }
            return coordinate;
        } else {
            throw new NMEAException();
        }
    }

    /**
     * Retorna a latitude de uma mensagem NMEA, convertendo-a de graus decimais
     * para graus-minutos decimais.
     * @param latString - Parte da mensagem NMEA que contém informação sobre latitude
     * @throws NMEAException se a mensagem recebida não estiver corretamente
     * formatada
     */
    private Double parseLatitude(String latString) throws NMEAException {
        if (latString.length() == 11) {
            Double coordinate;
            char hemisphere = latString.charAt(10);
            latString = latString.replace(".", "");
            int degrees = Integer.parseInt(latString.substring(0, 2));

            Double decimalMinutes = Double.parseDouble(latString.substring(2, 8)) / 10000;
            coordinate = degrees + decimalMinutes / 60;
            if (hemisphere == 'S') {
                coordinate = -coordinate;
            }
            return coordinate;
        } else {
            throw new NMEAException();
        }
    }

    /**
     * Retorna a velocidade de uma mensagem NMEA, convertendo-a de nós para Km/h
     * @param velString - Parte da mensagem NMEA que contém informação sobre velocidade
     * @throws NMEAException se a mensagem recebida não estiver corretamente
     * formatada
     */
    private Double parseVelocidade(String velString) throws NMEAException {
        Double velocidade;

        try {
            velocidade = Double.parseDouble(velString);
        } catch (NumberFormatException e) {
//            throw new NMEAException(e);
            return 0.0d;
        }
        velocidade = velocidade * 1.852;
        return velocidade;
    }

    /**
     * Converte um número que representa um horário em hora,minuto e segundo em
     * timestamp
     * @param hourMinSec
      */
    private Integer hourMinSecToTimestamp(int hourMinSec) {
        Calendar cal = Calendar.getInstance();


        return null;
    }

    // TODO Fazer pegar do servidor, tirar argumento BufferedReader e virar private
    public String read(BufferedReader br) throws IOException {
        return br.readLine();
    }

    // TODO remover
    public Posicao parseLine(BufferedReader br) throws Exception {
        String in = this.read(br);

        Ponto ponto = new Ponto();
        ponto.setLatitude(this.parseLatitude(in));
        ponto.setLongitude(this.parseLongitude(in));


        Posicao posicao = new Posicao();
        posicao.setPosicao(ponto);
        posicao.setHorario(this.parseHorario(in));

        return posicao;
    }

    public Double calculateDistance(Posicao p1, Posicao p2) {
        Double R = 6371D; // Raio da terra em Km
        Double dLat = Math.toRadians(p2.getPosicao().getLatitude() - p1.getPosicao().getLatitude());
        Double dLon = Math.toRadians(p2.getPosicao().getLongitude() - p1.getPosicao().getLongitude());

        Double lat1Radians = Math.toRadians(p1.getPosicao().getLatitude());
        Double lat2Radians = Math.toRadians(p2.getPosicao().getLatitude());

        Double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(lat1Radians) * Math.cos(lat2Radians) *
                Math.sin(dLon / 2) * java.lang.Math.sin(dLon / 2);
        Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
}
